//    Copyright (C) Kherty.  All rights reserved.
using System;

namespace OpenLS.Spreadsheet.AnalysisToolPak
{
    internal class ActualActualBasis : DateBasis
    {
        private static readonly DateTime origin = new DateTime(1900, 1, 1);

        protected override DateBasisType Type
        {
            get { return DateBasisType.ActualActual; }
        }

        internal override int DaysBetweenImp(DateTime start, DateTime end)
        {
            return
                ((int) (end - origin).TotalDays -
                 (int) (start - origin).TotalDays);
        }

        protected override double PerYear(DateTime start, DateTime end)
        {
            int startYear = start.Year;
            int endYear = end.Year;
            int feb29S, years;
            DateTime d1 = start;
            d1 = new DateTime(d1.Year + 1, d1.Month,
                              Math.Min(d1.Day, DateTime.DaysInMonth(d1.Year + 1, d1.Month)), d1.Hour,
                              d1.Minute, d1.Second);
            if (end.CompareTo(d1) > 0)
            {
                years = endYear + 1 - startYear;
                d1 = new DateTime(startYear, 1, 1);
                var d2 = new DateTime(endYear + 1, 1, 1);
                feb29S = (int) (d2 - origin).TotalDays -
                         (int) (d1 - origin).TotalDays -
                         365*(endYear + 1 - startYear);
            }
            else
            {
                years = 1;
                if ((DateTime.IsLeapYear(startYear) && start.Month < 3) ||
                    (DateTime.IsLeapYear(endYear) &&
                     (end.Month*0x100 + end.Day >= 2*0x100 + 29)))
                    feb29S = 1;
                else
                    feb29S = 0;
            }
            return 365 + (double) feb29S/years;
        }

        public override int DaysInYear(DateTime? dt)
        {
            if (dt == null)
                throw new NotSupportedException();
            if (DateTime.IsLeapYear(dt.Value.Year))
                return 366;
            return 365;
        }

        internal override double CoupDays(DateTime settlement, DateTime maturity, Frequency frequency)
        {
            DateTime d1 = CoupCd(settlement, maturity, frequency, true, false); //\\sure?
            DateTime d2 = CoupCd(settlement, maturity, frequency, true, true);
            return DayDifferenceImp(d2, d1);
        }

        internal override double YearDifference(DateTime d1, DateTime d2)
        {
            if (d1 < d2)
                return -YearDifference(d2, d1);
            bool moreThanOneYear = d2.AddYears(1) < d2;
            int years;
            double leapCount;
            if (moreThanOneYear)
            {
                years = d1.Year + 1 - d2.Year;
                var dd1 = new DateTime(d2.Year, 1, 1);
                var dd2 = new DateTime(d1.Year, 1, 1);
                leapCount = (dd2 - dd1).Days - 365*(d1.Year + 1 - d2.Year);
            }
            else
            {
                years = 1;
                if ((DateTime.IsLeapYear(d2.Year) && d2.Month < 3) ||
                    (DateTime.IsLeapYear(d1.Year) && ((d1.Month == 2 && d1.Day == 29) || (d1.Month > 2))))
                    leapCount = 1;
                else
                    leapCount = 0;
            }
            double dayPerYear = 365 + (leapCount/years);
            return (d1 - d2).Days/dayPerYear;
        }

        protected virtual int DaysBetweenImpOld(DateTime start, DateTime end)
        {
            return (end - start).Days;
        }

        internal static DateTime CoupCd(DateTime settlement, DateTime maturity, Frequency freq, bool endOfMonth,
                                        bool next)
        {
            int months, periods;
            DateTime result;
            bool isSpecial = endOfMonth && maturity.Day == DateTime.DaysInMonth(maturity.Year, maturity.Month);
            months = 12/(int) freq;
            periods = maturity.Year - settlement.Year;
            if (periods > 0)
                periods = (periods - 1)*(int) freq;
            do
            {
                result = maturity;
                periods++;
                result = result.AddMonths(-periods*months);
                if (isSpecial)
                {
                    int ndays = DateTime.DaysInMonth(result.Year, result.Month);
                    result = new DateTime(result.Year, result.Month, ndays);
                }
            } while (settlement < result);
            if (next)
            {
                result = maturity;
                periods--;
                result = result.AddMonths(-periods*months);
                if (isSpecial)
                {
                    int ndays = DateTime.DaysInMonth(result.Year, result.Month);
                    result = new DateTime(result.Year, result.Month, ndays);
                }
            }
            return result;
        }


        protected override int DayDifferenceImp(DateTime d1, DateTime d2)
        {
            int i1 = (d1 - new DateTime(1900, 1, 1)).Days;
            int i2 = (d2 - new DateTime(1900, 1, 1)).Days;
            return i1 - i2;
        }
    }
}